#include "Torneo.h"

Torneo::Torneo() {
	this->parsearArchivoConfiguracion();
	int puerto = this->getValorPropiedadConfiguracion(TORNEO_PUERTO);
	this->socket = new Socket(puerto);
	this->tiempoTorneo = this->getValorPropiedadConfiguracion(TORNEO_TIEMPO);
	ostringstream *ss = new ostringstream;
	*ss << TORNEO_CONTADOR_JUGADORES_DISPONIBLES << getpid();
	this->mutexPartidas = new ThreadMutex();
	this->mutexJugadoresDisponibles = new ThreadMutex();
	this->mutexRanking = new ThreadMutex();
	this->mutexTorneoFinalizado = new ThreadMutex();
	this->mutexTorneoFinalizado->P();										//Lo dejo tomado hasta que el reloj lo arranque
	this->finalizado = false;
	this->iniciado = false;
	this->administradorClientes = new AdministradorClientes(true);
	this->administradorPartidas = new AdministradorPartidas(true);
	this->terminadorPartidas = new TerminadorPartidas(true);
	this->administradorEstadisticas = new AdministradorEstadisticas(true);
	this->ingresoTeclado = new IngresoTeclado(false);
	this->reloj = new Reloj(true);
	this->torneoLogger = new Log(TORNEO_NOMBRE_CLASE);
}

int Torneo::getValorPropiedadConfiguracion(const char* propiedad) {
	return atoi(this->mapaConfiguracion[propiedad].c_str());
}

void Torneo::ejecutar() {
	this->inicializarSocket();
	this->inicializarThreads();
	this->iniciarThreads();
	this->torneoLogger->info("El torneo comenzará cuando se conecten al menos dos jugadores");
	this->mutexTorneoFinalizado->P();
	if(!this->isFinalizado())
		this->terminarTorneo(false);
}

void Torneo::inicializarSocket() {
	//Realizo el bind del socket del torneo
	int bindSocket = this->socket->bindSocket();
	int listenSocket = this->socket->listenSocket();
	if (bindSocket || listenSocket) {
		this->terminarTorneo(false);
	}
}

void Torneo::calcularTiempoTorneo() {
	this->torneoLogger->info("Calculando tiempo de torneo");
	this->tiempoInicial = time(0);
	this->tiempoFinal = this->tiempoInicial + this->tiempoTorneo * 60;
}

void Torneo::parsearArchivoConfiguracion() {
	std::ifstream infile(TORNEO_ARCHIVO_CONFIGURACION);
	std::string line;
	while (std::getline(infile, line)) {
		string clave = line.substr(0, line.find('='));
		string valor = line.substr(line.find('=') + 1, line.length() - 1);
		this->mapaConfiguracion[clave] = valor;
	}

}

void Torneo::iniciarThreads() {
	this->torneoLogger->info("Iniciando threads");
	this->administradorClientes->start();
	this->administradorPartidas->start();
	this->terminadorPartidas->start();
}

void Torneo::inicializarThreads() {
	this->torneoLogger->info("Inicializando threads");
	this->administradorClientes->setTorneo(this);
	this->ingresoTeclado->setTorneo(this);
	this->administradorPartidas->setTorneo(this);
	this->terminadorPartidas->setTorneo(this);
	this->reloj->setTorneo(this);
	this->administradorEstadisticas->setTorneo(this);
}

void Torneo::desenlistarJugadorDisponible(std::list<t_jugador>::iterator it) {
	this->torneoLogger->info(string("Desenlistando jugador disponible: ") + string(it->nombre) + string("\0"));
	this->mutexJugadoresDisponibles->P();
	this->jugadoresDisponibles.erase(it);
	this->mutexJugadoresDisponibles->V();
}

void Torneo::enlistarJugadorDisponible(t_jugador* jugador) {
	this->torneoLogger->info(string("Enlistando jugador disponible: ") + string(jugador->nombre) + string("\0"));
	this->mutexJugadoresDisponibles->P();
	this->jugadoresDisponibles.push_back(*jugador);
	this->mutexJugadoresDisponibles->V();
}

void Torneo::enlistarJugadorRanking(t_jugador* jugador) {
	this->torneoLogger->info(string("Enlistando jugador en ranking: ") + string(jugador->nombre) + string("\0"));
	t_estadistica_jugador* estadistica = new t_estadistica_jugador();
	estadistica->jugador = *jugador;
	estadistica->total_puntaje = jugador->puntaje;
	this->mutexRanking->P();
	this->ranking.push_back(*estadistica);
	this->mutexRanking->V();
}

void Torneo::esperarPartidas(bool hayError) {
	bool hayPartidas = true;
	int status;

	std::list<t_partida>::iterator iterator1;
	for (iterator1 = this->partidas.begin(); iterator1 != this->partidas.end(); ++iterator1) {
		/* Envio señal de fin de torneo a las partidas, si hay error envio señal de error */
		if(hayError)
			kill(iterator1->partidaId, SENIAL_FIN_TORNEO_ERROR);
		else
			kill(iterator1->partidaId, SENIAL_FIN_TORNEO);
	}

	/* Mientras que haya partidas sin terminar sigo */
	while(hayPartidas){
		hayPartidas = false;
		std::list<t_partida>::iterator iterator;
		for (iterator = this->partidas.begin(); iterator != this->partidas.end(); ++iterator) {
			if(iterator->activo ){
				if (waitpid(iterator->partidaId, &status, WNOHANG) > 0) {
					/* Libero sus recursos */
					iterator->activo = false;
					this->administradorMemoriaCompartida->conectarShm(iterator->partidaId);
					this->administradorMemoriaCompartida->cerrarShm(iterator->partidaId);
					this->administradorMemoriaCompartida->eliminarSemaforo(iterator->partidaId);
					this->socket->closeSocket(iterator->jugador1.socket_id);
					this->socket->closeSocket(iterator->jugador2.socket_id);
				}else {
					/*Si no libero, me fijo si no esta corriendo, de ser así es porque la mataron a mano y no la tome todavia */
					if(kill(iterator->partidaId, 0) == -1){
						/* notifico a sus jugadores el fin del torneo */
						notificarFinTorneo(true, iterator->jugador1.socket_id);
						notificarFinTorneo(true, iterator->jugador2.socket_id);
						this->socket->closeSocket(iterator->jugador1.socket_id);
						this->socket->closeSocket(iterator->jugador2.socket_id);
						/* Libero sus recursos */
						iterator->activo = false;
						this->administradorMemoriaCompartida->conectarShm(iterator->partidaId);
						this->administradorMemoriaCompartida->cerrarShm(iterator->partidaId);
						this->administradorMemoriaCompartida->eliminarSemaforo(iterator->partidaId);
					}else{
						hayPartidas = true;
					}
				}
			}
		}
		usleep(100000);
	}
}

/* Notifica a los jugadores que termino la partida por si termino mal */
void Torneo::notificarFinPartida(int socketJ){
	/* Genero la accion para que los clientes finalicen el torneo */
	t_accion a;
	a.id_accion = ACC_FIN_PARTIDA;

	/* Envio el fin a los jugadores */
	this->socket->write((void *)&a, sizeof(a), socketJ);
}

/* Notifica a los jugadores que el torneo termino de forma inesperada o no */
void Torneo::notificarFinTorneo(bool hayError, int socketJ){
	/* Genero la accion para que los clientes finalicen el torneo */
	t_accion a;
	a.id_accion = ACC_FIN_TORNEO;

	/* Señal que indica si el fin es con error o sin error */
	if(hayError)
		a.mov_x = 1;
	else
		a.mov_x = 0;

	/* Envio el fin a los jugadores */
	this->socket->write((void *)&a, sizeof(a), socketJ);
}

void Torneo::matarPartidas() {
	std::list<t_partida>::const_iterator iterator;
	for (iterator = this->partidas.begin(); iterator != this->partidas.end(); ++iterator) {
		kill(iterator->partidaId, SENIAL_FIN_TORNEO_ERROR);
	}
}

void Torneo::abortarTorneo() {
	this->torneoLogger->error("Error en la ejecucion.");
	this->finalizado = true;
	sleep(1);					//Para que espere a que los threads se bajen.
	this->administradorMemoriaCompartida->liberarTodo();
	this->terminarTorneo(true);
}

void Torneo::terminarTorneo(bool hayError) {
	this->setFinalizado(true);
	this->torneoLogger->info("Finalizando torneo");
	this->juntarThreads();
	this->esperarPartidas(hayError);
	this->finalizarJugadoresDisponibles(hayError);
	this->torneoLogger->info("Torneo finalizado");
	sleep(5);
	this->socket->closeSocket();
	exit(0);
}

void Torneo::finalizarJugadoresDisponibles(bool hayError){
	this->getMutexJugadoresDisponibles()->P();
	std::list<t_jugador>::iterator it = this->getJugadoresDisponibles().begin();
	for (; it != this->getJugadoresDisponibles().end(); ++it) {
		notificarFinTorneo(hayError, it->socket_id);
	}
	this->getMutexJugadoresDisponibles()->V();
}

void Torneo::juntarThreads() {
	/* Primero los mato a los que estan bloqueados */
	pthread_kill(this->administradorClientes->get_id(), SIGURG);
	this->administradorEstadisticas->join();
	this->administradorPartidas->join();
	this->terminadorPartidas->join();
	this->reloj->join();
	this->administradorClientes->join();
}

//getters & stters

AdministradorMemoriaCompartida* Torneo::getAdministradorMemoriaCompartida() {
	return administradorMemoriaCompartida;
}

void Torneo::setAdministradorMemoriaCompartida(AdministradorMemoriaCompartida* administradorMemoriaCompartida) {
	this->administradorMemoriaCompartida = administradorMemoriaCompartida;
}

list<t_jugador>& Torneo::getJugadoresDisponibles() {
	return jugadoresDisponibles;
}

void Torneo::setJugadoresDisponibles(list<t_jugador>& jugadoresDisponibles) {
	this->jugadoresDisponibles = jugadoresDisponibles;
}

list<t_partida>& Torneo::getPartidas(){
	return partidas;
}

void Torneo::setPartidas(list<t_partida>& partidas) {
	this->partidas = partidas;
}

int Torneo::getTiempoTorneo() {
	return tiempoTorneo;
}

Semaforo* Torneo::getContadorJugadoresDisponibles() {
	return contadorJugadoresDisponibles;
}

void Torneo::setContadorJugadoresDisponibles(Semaforo* contadorJugadoresDisponibles) {
	this->contadorJugadoresDisponibles = contadorJugadoresDisponibles;
}

ThreadMutex* Torneo::getMutexJugadoresDisponibles() {
	return mutexJugadoresDisponibles;
}

void Torneo::setMutexJugadoresDisponibles(ThreadMutex* mutexJugadoresDisponibles) {
	this->mutexJugadoresDisponibles = mutexJugadoresDisponibles;
}

void Torneo::setTiempoTorneo(int tiempoTorneo) {
	this->tiempoTorneo = tiempoTorneo;
}

Socket* Torneo::getSocket() {
	return socket;
}

void Torneo::setSocket(Socket* socket) {
	this->socket = socket;
}

ThreadMutex* Torneo::getMutexPartidas() {
	return mutexPartidas;
}

void Torneo::setMutexPartidas(ThreadMutex* mutexPartidas) {
	this->mutexPartidas = mutexPartidas;
}

list<t_estadistica_jugador> *Torneo::getRanking() {
	return &ranking;
}

time_t Torneo::getTiempoFinal() {
	return tiempoFinal;
}

void Torneo::setTiempoFinal(time_t tiempoFinal) {
	this->tiempoFinal = tiempoFinal;
}

time_t Torneo::getTiempoInicial() {
	return tiempoInicial;
}

bool Torneo::isFinalizado() {
	return finalizado;
}

time_t* Torneo::getTiempoActual() {
	return &tiempoActual;
}

Reloj* Torneo::getReloj() {
	return reloj;
}

void Torneo::setReloj(Reloj* reloj) {
	this->reloj = reloj;
}

void Torneo::setTiempoActual(time_t tiempoActual) {
	this->tiempoActual = tiempoActual;
}

void Torneo::setFinalizado(bool finalizado) {
	this->finalizado = finalizado;
}

void Torneo::setTiempoInicial(time_t tiempoInicial) {
	this->tiempoInicial = tiempoInicial;
}

void Torneo::setRanking(list<t_estadistica_jugador> *ranking) {
	this->ranking = *ranking;
}

Log* Torneo::getTorneoLogger() {
	return torneoLogger;
}

void Torneo::setTorneoLogger(Log* torneoLogger) {
	this->torneoLogger = torneoLogger;
}

AdministradorEstadisticas* Torneo::getAdministradorEstadisticas() {
	return administradorEstadisticas;
}

void Torneo::setAdministradorEstadisticas(AdministradorEstadisticas* administradorEstadisticas) {
	this->administradorEstadisticas = administradorEstadisticas;
}

ThreadMutex* Torneo::getMutexRanking() {
	return mutexRanking;
}

void Torneo::setMutexRanking(ThreadMutex* mutexRanking) {
	this->mutexRanking = mutexRanking;
}

bool Torneo::isIniciado() {
	return iniciado;
}

void Torneo::setIniciado(bool iniciado) {
	this->iniciado = iniciado;
}

ThreadMutex* Torneo::getMutexTorneoFinalizado() {
	return mutexTorneoFinalizado;
}

void Torneo::setMutexTorneoFinalizado(ThreadMutex* mutexTorneoFinalizado) {
	this->mutexTorneoFinalizado = mutexTorneoFinalizado;
}

int Torneo::getCnPartidas(){
	return this->cnPartidas;
}

void Torneo::addPartidas(){
	this->cnPartidas++;
}

void Torneo::decPartidas(){
	this->cnPartidas--;
}

IngresoTeclado *Torneo::getIngresoTeclado(){
	return this->ingresoTeclado;
}

void Torneo::setMostrarPartidas(bool mostrarPartidas){
	this->mostrarPartidas = mostrarPartidas;
}

bool Torneo::isMostrarPartidas(){
	return this->mostrarPartidas;
}
